/*
 * Copyright (c) 2002-2003 by OpenSymphony
 * All rights reserved.
 */
package org.jackysoft.opensymphony.workflow.entity;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jackysoft.util.ValidationUtils;

/**
 * Describes a single workflow
 * 
 * 
 */

public class WorkflowDescriptor extends AbstractDescriptor implements
		Validatable {
	/**
	 * 
	 */
	private static final long serialVersionUID = -3331610649452104841L;
	// ~ Static fields/initializers
	// /////////////////////////////////////////////

	public static final String XML_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
	public static final String DOCTYPE_DECL = "<!DOCTYPE workflow PUBLIC \"-//OpenSymphony Group//DTD OSWorkflow 2.8//EN\" \"http://www.opensymphony.com/osworkflow/workflow_2_8.dtd\">";

	// ~ Instance fields
	// ////////////////////////////////////////////////////////

	protected ConditionsDescriptor globalConditions = null;
	protected List commonActionsList = new ArrayList(); // for preserving order
	protected List globalActions = new ArrayList();
	protected List initialActions = new ArrayList();
	protected List joins = new ArrayList();
	protected List registers = new ArrayList();
	protected List splits = new ArrayList();
	protected List steps = new ArrayList();
	protected Map commonActions = new HashMap();
	protected Map metaAttributes = new HashMap();
	protected Map timerFunctions = new HashMap();
	protected String workflowName = null;

	// ~ Constructors
	// ///////////////////////////////////////////////////////////

	/**
	 * 
	 */
	public WorkflowDescriptor() {
	}

	/**
	 * 
	 */
	public WorkflowDescriptor(org.dom4j.Element element) {
		this.element = element;
		this.init(element);
	}

	// ~ Methods
	// ////////////////////////////////////////////////////////////////

	public ActionDescriptor getAction(int id) {
		// check global actions
		for (Iterator iterator = globalActions.iterator(); iterator.hasNext();) {
			ActionDescriptor actionDescriptor = (ActionDescriptor) iterator
					.next();

			if (actionDescriptor.getId() == id) {
				return actionDescriptor;
			}
		}

		// check steps
		for (Iterator iterator = steps.iterator(); iterator.hasNext();) {
			StepDescriptor stepDescriptor = (StepDescriptor) iterator.next();
			ActionDescriptor actionDescriptor = stepDescriptor.getAction(id);

			if (actionDescriptor != null) {
				return actionDescriptor;
			}
		}

		// check initial actions, which we now must have unique id's
		for (Iterator iterator = initialActions.iterator(); iterator.hasNext();) {
			ActionDescriptor actionDescriptor = (ActionDescriptor) iterator
					.next();

			if (actionDescriptor.getId() == id) {
				return actionDescriptor;
			}
		}

		return null;
	}

	/*
	 * joinColumns={ ,inverseJoinColumns={ )
	 */

	//

	//
	public Map getCommonActions() {
		return commonActions;
	}

	//
	public List getGlobalActions() {
		return globalActions;
	}

	//
	public ConditionsDescriptor getGlobalConditions() {
		return globalConditions;
	}

	public ActionDescriptor getInitialAction(int id) {
		for (Iterator iterator = initialActions.iterator(); iterator.hasNext();) {
			ActionDescriptor actionDescriptor = (ActionDescriptor) iterator
					.next();

			if (actionDescriptor.getId() == id) {
				return actionDescriptor;
			}
		}

		return null;
	}

	//
	public List getInitialActions() {
		return initialActions;
	}

	public JoinDescriptor getJoin(int id) {
		for (Iterator iterator = joins.iterator(); iterator.hasNext();) {
			JoinDescriptor joinDescriptor = (JoinDescriptor) iterator.next();

			if (joinDescriptor.getId() == id) {
				return joinDescriptor;
			}
		}

		return null;
	}

	//
	public List getJoins() {
		return joins;
	}

	//
	public Map getMetaAttributes() {
		return metaAttributes;
	}

	public void setName(String name) {
		workflowName = name;
	}

	public String getName() {
		return workflowName;
	}

	//
	public List getRegisters() {
		return registers;
	}

	public SplitDescriptor getSplit(int id) {
		for (Iterator iterator = splits.iterator(); iterator.hasNext();) {
			SplitDescriptor splitDescriptor = (SplitDescriptor) iterator.next();

			if (splitDescriptor.getId() == id) {
				return splitDescriptor;
			}
		}

		return null;
	}

	//
	public List getSplits() {
		return splits;
	}

	public StepDescriptor getStep(int id) {
		for (Iterator iterator = steps.iterator(); iterator.hasNext();) {
			StepDescriptor step = (StepDescriptor) iterator.next();

			if (step.getId() == id) {
				return step;
			}
		}

		return null;
	}

	//
	public List getSteps() {

		return steps;
	}

	/**
	 * Update a trigger function
	 * 
	 * 
	 * The id for the trigger function
	 * 
	 * The descriptor for the trigger function
	 * 
	 */
	public FunctionDescriptor setTriggerFunction(int id,
			FunctionDescriptor descriptor) {
		return (FunctionDescriptor) timerFunctions.put(new Integer(id),
				descriptor);
	}

	public FunctionDescriptor getTriggerFunction(int id) {
		return (FunctionDescriptor) this.timerFunctions.get(new Integer(id));
	}

	/**
	 * Get a Map of all trigger functions in this workflow
	 * 
	 * 
	 */

	public Map getTriggerFunctions() {
		return timerFunctions;
	}

	/**
	 * Add a common action
	 * 
	 * 
	 * The action descriptor to add
	 * 
	 * if the descriptor's ID already exists in the workflow
	 */
	public void addCommonAction(ActionDescriptor descriptor) {
		descriptor.setCommon(true);
		addAction(commonActions, descriptor);
		addAction(commonActionsList, descriptor);
	}

	/**
	 * Add a global action
	 * 
	 * 
	 * The action descriptor to add
	 * 
	 * if the descriptor's ID already exists in the workflow
	 */
	public void addGlobalAction(ActionDescriptor descriptor) {
		addAction(globalActions, descriptor);
	}

	/**
	 * Add an initial action
	 * 
	 * 
	 * The action descriptor to add
	 * 
	 * if the descriptor's ID already exists in the workflow
	 */
	public void addInitialAction(ActionDescriptor descriptor) {
		addAction(initialActions, descriptor);
	}

	/**
	 * Add a join
	 * 
	 * 
	 * The join descriptor to add
	 * 
	 * if the descriptor's ID already exists in the workflow
	 */
	public void addJoin(JoinDescriptor descriptor) {
		if (getJoin(descriptor.getId()) != null) {
			throw new IllegalArgumentException("Join with id "
					+ descriptor.getId() + " already exists");
		}

		joins.add(descriptor);
	}

	/**
	 * Add a split
	 * 
	 * 
	 * The split descriptor to add
	 * 
	 * if the descriptor's ID already exists in the workflow
	 */
	public void addSplit(SplitDescriptor descriptor) {
		if (getSplit(descriptor.getId()) != null) {
			throw new IllegalArgumentException("Split with id "
					+ descriptor.getId() + " already exists");
		}

		splits.add(descriptor);
	}

	/**
	 * Add a step
	 * 
	 * 
	 * The step descriptor to add
	 * 
	 * if the descriptor's ID already exists in the workflow
	 */
	public void addStep(StepDescriptor descriptor) {
		if (getStep(descriptor.getId()) != null) {
			throw new IllegalArgumentException("Step with id "
					+ descriptor.getId() + " already exists");
		}

		steps.add(descriptor);
	}

	/**
	 * Remove an action from this workflow completely.
	 * <p>
	 * This method will check global actions and all steps.
	 * 
	 * 
	 * found
	 */
	public boolean removeAction(ActionDescriptor actionToRemove) {
		// global actions
		for (Iterator iterator = getGlobalActions().iterator(); iterator
				.hasNext();) {
			ActionDescriptor actionDescriptor = (ActionDescriptor) iterator
					.next();

			if (actionDescriptor.getId() == actionToRemove.getId()) {
				getGlobalActions().remove(actionDescriptor);

				return true;
			}
		}

		// steps
		for (Iterator iterator = getSteps().iterator(); iterator.hasNext();) {
			StepDescriptor stepDescriptor = (StepDescriptor) iterator.next();
			ActionDescriptor actionDescriptor = stepDescriptor
					.getAction(actionToRemove.getId());

			if (actionDescriptor != null) {
				stepDescriptor.getActions().remove(actionDescriptor);

				return true;
			}
		}

		return false;
	}

	public void validate() throws InvalidWorkflowDescriptorException {
		ValidationUtils.validate(this.getRegisters());
		ValidationUtils.validate(this.getTriggerFunctions().values());
		ValidationUtils.validate(this.getGlobalActions());
		ValidationUtils.validate(this.getInitialActions());
		ValidationUtils.validate(this.getCommonActions().values());
		ValidationUtils.validate(this.getSteps());
		ValidationUtils.validate(this.getSplits());
		ValidationUtils.validate(this.getJoins());

		Set actions = new HashSet();
		Iterator i = globalActions.iterator();

		while (i.hasNext()) {
			ActionDescriptor action = (ActionDescriptor) i.next();
			actions.add(new Integer(action.getId()));
		}

		i = getSteps().iterator();

		while (i.hasNext()) {
			StepDescriptor step = (StepDescriptor) i.next();
			Iterator j = step.getActions().iterator();

			while (j.hasNext()) {
				ActionDescriptor action = (ActionDescriptor) j.next();

				// check to see if it's a common action (dups are ok, since
				// that's the point of common actions!)
				if (!action.isCommon()) {
					if (!actions.add(new Integer(action.getId()))) {
						throw new InvalidWorkflowDescriptorException(
								"Duplicate occurance of action ID "
										+ action.getId() + " found in step "
										+ step.getId());
					}
				}
			}
		}

		// now we have all our unique actions, let's check that no common action
		// id's exist in them
		i = commonActions.keySet().iterator();

		while (i.hasNext()) {
			Integer action = (Integer) i.next();

			if (actions.contains(action)) {
				throw new InvalidWorkflowDescriptorException(
						"common-action ID " + action
								+ " is duplicated in a step action");
			}
		}

		i = initialActions.iterator();

		while (i.hasNext()) {
			ActionDescriptor action = (ActionDescriptor) i.next();

			if (actions.contains(new Integer(action.getId()))) {
				throw new InvalidWorkflowDescriptorException(
						"initial-action ID " + action
								+ " is duplicated in a step action");
			}
		}

		validateDTD();
	}

	@Override
	protected void init(org.dom4j.Element root) {
		commonActionsList = new ArrayList(); // for preserving order
		globalActions = new ArrayList();
		initialActions = new ArrayList();
		joins = new ArrayList();
		registers = new ArrayList();
		splits = new ArrayList();
		steps = new ArrayList();
		commonActions = new HashMap();
		metaAttributes = new HashMap();
		timerFunctions = new HashMap();
		for (Iterator i = root.elementIterator(); i.hasNext();) {
			org.dom4j.Element child = (org.dom4j.Element) i.next();
			String nodeName = child.getName();
			if ("meta".equalsIgnoreCase(nodeName)) {
				this.metaAttributes.put(child.attributeValue("name"),
						child.getText());
			}
			if ("registers".equalsIgnoreCase(nodeName)) {
				buildElements(child, "register", RegisterDescriptor.class,
						this.registers, this);
			}
			if ("global-conditions".equalsIgnoreCase(nodeName)) {
				org.dom4j.Element globalConditions = child
						.element("conditions");
				ConditionsDescriptor conditionsDescriptor = new ConditionsDescriptor(
						globalConditions);
				conditionsDescriptor.setParent(this);
				this.globalConditions = conditionsDescriptor;
			}
			if ("initial-actions".equals(nodeName)) {
				buildElements(child, "action", ActionDescriptor.class,
						this.initialActions, this);
			}
			if ("global-actions".equals(nodeName)) {
				buildElements(child, "action", ActionDescriptor.class,
						this.globalActions, this);
			}
			if ("common-actions".equals(nodeName)) {
				buildElements(child, "action", ActionDescriptor.class, this,
						new BuildCallback() {

							@Override
							public void doExecute(Object obj,
									org.dom4j.Element element) {
								addCommonAction((ActionDescriptor) obj);
							}

						});
			}
			if ("trigger-functions".equals(nodeName)) {
				buildElements(child, "trigger-function",
						FunctionDescriptor.class, this, new BuildCallback() {

							@Override
							public void doExecute(Object obj,
									org.dom4j.Element element) {
								try {
									Integer id = new Integer(element
											.attributeValue("id"));
									timerFunctions.put(id, obj);
								} catch (NumberFormatException e) {
								}

							}

						});
			}
			if ("steps".equals(nodeName)) {
				super.buildElements(child, "step", StepDescriptor.class,
						this.steps, this);
			}
			if ("splits".equals(nodeName)) {
				super.buildElement(child, "splits", "split",
						SplitDescriptor.class, this.splits, this);
			}
			if ("joins".equals(nodeName)) {
				super.buildElement(child, "joins", "join",
						JoinDescriptor.class, this.joins, this);
			}

		}
	}

	// refactored this out from the three addAction methods above
	private void addAction(Object actionsCollectionOrMap,
			ActionDescriptor descriptor) {
		if (getAction(descriptor.getId()) != null) {
			throw new IllegalArgumentException("action with id "
					+ descriptor.getId() + " already exists for this step.");
		}

		if (actionsCollectionOrMap instanceof Map) {
			((Map) actionsCollectionOrMap).put(new Integer(descriptor.getId()),
					descriptor);
		} else {
			((Collection) actionsCollectionOrMap).add(descriptor);
		}
	}

	private void validateDTD() throws InvalidWorkflowDescriptorException {

	}

	//
	public List getCommonActionsList() {
		return commonActionsList;
	}

	//
	public Map getTimerFunctions() {
		return timerFunctions;
	}

	//

	public String getWorkflowName() {
		return workflowName;
	}

	/**
	 * 
	 */
	public void setGlobalConditions(ConditionsDescriptor globalConditions) {
		this.globalConditions = globalConditions;
	}

	/**
	 * 
	 */
	public void setCommonActionsList(List commonActionsList) {
		this.commonActionsList = commonActionsList;
	}

	/**
	 * 
	 */
	public void setGlobalActions(List globalActions) {
		this.globalActions = globalActions;
	}

	/**
	 * 
	 */
	public void setInitialActions(List initialActions) {
		this.initialActions = initialActions;
	}

	/**
	 * 
	 */
	public void setJoins(List joins) {
		this.joins = joins;
	}

	/**
	 * 
	 */
	public void setRegisters(List registers) {
		this.registers = registers;
	}

	/**
	 * 
	 */
	public void setSplits(List splits) {
		this.splits = splits;
	}

	/**
	 * 
	 */
	public void setSteps(List steps) {
		this.steps = steps;
	}

	/**
	 * 
	 */
	public void setCommonActions(Map commonActions) {
		this.commonActions = commonActions;
	}

	/**
	 * 
	 */
	public void setMetaAttributes(Map metaAttributes) {
		this.metaAttributes = metaAttributes;
	}

	/**
	 * 
	 */
	public void setTimerFunctions(Map timerFunctions) {
		this.timerFunctions = timerFunctions;
	}

	/**
	 * 
	 */
	public void setWorkflowName(String workflowName) {
		this.workflowName = workflowName;
	}

}
